import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Content } from './entities/content.entity';
import { CreateContentDto } from './dto/create-content.dto';
import { UpdateContentDto } from './dto/update-content.dto';
import { QueryContentDto } from './dto/query-content.dto';
import { PaginatedResponseDto, PaginationMetaDto } from '../common/dto/pagination.dto';
import { ContentResponseDto, ContentDetailResponseDto } from './dto/content-response.dto';
import { TagsService } from '../tags/tags.service';
import { UserQueryContentDto } from './dto/user-query-content.dto';
import { ContentStatus } from '../common/enums';
import { AttachmentsService } from '../attachments/services/attachments.service';
import { Attachment } from './entities/attachment.entity';
import { AttachmentResponseDto } from '../attachments/dto/attachment-response.dto';
import { UserRole } from '../common/enums';

@Injectable()
export class ContentsService {
  constructor(
    @InjectRepository(Content)
    private contentsRepository: Repository<Content>,
    private tagsService: TagsService,
    @InjectRepository(Attachment)
    private attachmentRepository: Repository<Attachment>,
    private attachmentsService: AttachmentsService,
  ) {}

  // Helper method to map Attachment entity to AttachmentResponseDto
  private mapAttachmentToDto(attachment: Attachment): AttachmentResponseDto {
    return {
      id: attachment.id,
      fileUrl: attachment.fileUrl,
      filename: attachment.fileName,
      createdBy: attachment.createdBy,
      createdAt: attachment.createdAt,
    };
  }

  async create(
    createContentDto: CreateContentDto,
    userId: string,
    tagIds?: string[],
  ): Promise<Content> {
    const { attachmentIds, ...contentData } = createContentDto;

    // Create the content with the user ID and set isApproved to false by default
    const content = this.contentsRepository.create({
      ...contentData,
      createdBy: userId,
      isApproved: false,
    });
    const savedContent = await this.contentsRepository.save(content);

    // Associate tags if provided
    if (tagIds && tagIds.length > 0) {
      await Promise.all(
        tagIds.map((tagId) => this.tagsService.addContentTag(savedContent.id, tagId)),
      );
    }

    // Associate attachments if provided
    if (attachmentIds && attachmentIds.length > 0) {
      await Promise.all(
        attachmentIds.map(async (attachmentId) => {
          const attachment = await this.attachmentRepository.findOne({
            where: { id: attachmentId },
          });
          if (attachment) {
            attachment.contentId = savedContent.id;
            await this.attachmentRepository.save(attachment);
          }
        }),
      );
    }

    return savedContent;
  }

  async findAll(
    queryDto: QueryContentDto,
  ): Promise<PaginatedResponseDto<ContentDetailResponseDto>> {
    const {
      page = 1,
      pageSize = 20,
      title,
      contentType,
      createdBy,
      templateId,
      isApproved,
      includeDeleted = false,
      tagIds,
    } = queryDto;

    // Calculate skip for pagination
    const skip = (page - 1) * pageSize;

    // Build query
    const queryBuilder = this.contentsRepository
      .createQueryBuilder('content')
      .leftJoinAndSelect('content.attachments', 'attachment')
      .leftJoinAndSelect('content.template', 'template');

    // Add filters
    if (title) {
      queryBuilder.andWhere('content.title LIKE :title', { title: `%${title}%` });
    }

    if (contentType) {
      queryBuilder.andWhere('content.contentType = :contentType', { contentType });
    }

    if (createdBy) {
      queryBuilder.andWhere('content.createdBy = :createdBy', { createdBy });
    }

    if (templateId) {
      queryBuilder.andWhere('content.templateId = :templateId', { templateId });
    }

    if (isApproved !== undefined) {
      queryBuilder.andWhere('content.isApproved = :isApproved', { isApproved });
    }

    // Handle deleted content
    if (!includeDeleted) {
      queryBuilder.andWhere('content.isDeleted = :isDeleted', { isDeleted: false });
    }

    // Add tag filtering if tagIds is provided
    if (tagIds && tagIds.length > 0) {
      // For each tag, create a subquery finding content that has this tag
      const subqueries = tagIds.map((tagId, index) => {
        return `content.id IN (
          SELECT content_id FROM content_tag_mapping 
          WHERE tag_id = :tagId${index}
        )`;
      });

      // Add conditions to the main query requiring ALL specified tags
      const tagCondition = subqueries.join(' AND ');

      // Add parameters for each tag ID
      const tagParams = {};
      tagIds.forEach((tagId, index) => {
        tagParams[`tagId${index}`] = tagId;
      });

      queryBuilder.andWhere(tagCondition, tagParams);
    }

    // Get total count
    const total = await queryBuilder.getCount();

    // Get paginated results
    const contents = await queryBuilder
      .skip(skip)
      .take(pageSize)
      .orderBy('content.createdAt', 'DESC')
      .getMany();

    // Create pagination metadata
    const pagination: PaginationMetaDto = {
      total,
      page,
      pageSize,
    };

    // Map to response DTOs with tags
    const contentWithTagsPromises = contents.map(async (content) => {
      const dto = new ContentDetailResponseDto();
      Object.assign(dto, content);

      // Get tags for this content
      dto.tags = await this.tagsService.getContentTags(content.id);

      // Get attachments for this content
      const attachments = await this.attachmentsService.findByContentId(content.id);
      dto.attachments = attachments.map((attachment) => this.mapAttachmentToDto(attachment));

      return dto;
    });

    const contentWithTags = await Promise.all(contentWithTagsPromises);

    return {
      data: contentWithTags,
      pagination,
    };
  }

  async findMyContents(
    userId: string,
    queryDto: UserQueryContentDto,
  ): Promise<PaginatedResponseDto<ContentDetailResponseDto>> {
    const {
      page = 1,
      pageSize = 20,
      title,
      contentType,
      templateId,
      contentStatus = [],
    } = queryDto;

    // Calculate skip for pagination
    const skip = (page - 1) * pageSize;

    // Build query
    const queryBuilder = this.contentsRepository
      .createQueryBuilder('content')
      .leftJoinAndSelect('content.attachments', 'attachment')
      .leftJoinAndSelect('content.template', 'template');

    // Add filters
    if (title) {
      queryBuilder.andWhere('content.title LIKE :title', { title: `%${title}%` });
    }

    if (contentType) {
      queryBuilder.andWhere('content.contentType = :contentType', { contentType });
    }

    if (templateId) {
      queryBuilder.andWhere('content.templateId = :templateId', { templateId });
    }

    // Always filter out deleted content
    queryBuilder.andWhere('content.isDeleted = :isDeleted', { isDeleted: false });

    // Apply content status filters
    if (contentStatus.length > 0) {
      const conditions = [];
      const params = {};

      // Process each content status
      contentStatus.forEach((status, index) => {
        switch (status) {
          case ContentStatus.SELF_CREATED_APPROVED:
            conditions.push(`(content.createdBy = :userId${index} AND content.isApproved = true)`);
            params[`userId${index}`] = userId;
            break;
          case ContentStatus.SELF_CREATED_UNAPPROVED:
            conditions.push(`(content.createdBy = :userId${index} AND content.isApproved = false)`);
            params[`userId${index}`] = userId;
            break;
          case ContentStatus.OTHERS_CREATED_APPROVED:
            conditions.push(`(content.createdBy != :userId${index} AND content.isApproved = true)`);
            params[`userId${index}`] = userId;
            break;
        }
      });

      if (conditions.length > 0) {
        queryBuilder.andWhere(`(${conditions.join(' OR ')})`, params);
      }
    } else {
      // Default behavior: show all content created by the user or approved content created by others
      queryBuilder.andWhere('(content.createdBy = :userId OR content.isApproved = true)', {
        userId,
      });
    }

    // Get total count
    const total = await queryBuilder.getCount();

    // Get paginated results
    const contents = await queryBuilder
      .skip(skip)
      .take(pageSize)
      .orderBy('content.createdAt', 'DESC')
      .getMany();

    // Create pagination metadata
    const pagination: PaginationMetaDto = {
      total,
      page,
      pageSize,
    };

    // Map to response DTOs with tags
    const contentWithTagsPromises = contents.map(async (content) => {
      const dto = new ContentDetailResponseDto();
      Object.assign(dto, content);

      // Get tags for this content
      dto.tags = await this.tagsService.getContentTags(content.id);

      // Get attachments for this content
      const attachments = await this.attachmentsService.findByContentId(content.id);
      dto.attachments = attachments.map((attachment) => this.mapAttachmentToDto(attachment));

      return dto;
    });

    const contentWithTags = await Promise.all(contentWithTagsPromises);

    return {
      data: contentWithTags,
      pagination,
    };
  }

  async findOne(id: string, userId: string, userRole: UserRole): Promise<ContentDetailResponseDto> {
    const content = await this.contentsRepository.findOne({
      where: { id, isDeleted: false },
      relations: ['attachments', 'template'],
    });

    if (!content) {
      throw new NotFoundException(`Content with ID ${id} not found`);
    }

    // Check permissions
    if (userRole !== UserRole.ADMIN && content.createdBy !== userId && !content.isApproved) {
      throw new ForbiddenException('You do not have permission to access this content');
    }

    // Convert to DTO with tags
    const contentWithTags = new ContentDetailResponseDto();
    Object.assign(contentWithTags, content);

    // Get tags for this content
    contentWithTags.tags = await this.tagsService.getContentTags(content.id);

    // Get attachments for this content (they should already be loaded via relations)
    contentWithTags.attachments = content.attachments
      ? content.attachments.map((attachment) => this.mapAttachmentToDto(attachment))
      : [];

    return contentWithTags;
  }

  async update(
    id: string,
    updateContentDto: UpdateContentDto,
    userId: string,
    userRole: UserRole,
  ): Promise<Content> {
    const content = await this.getContentEntity(id);

    // Check if user is admin or the creator of the content
    if (userRole !== UserRole.ADMIN && content.createdBy !== userId) {
      throw new ForbiddenException('You can only update your own content');
    }

    const { tagIds, attachmentIds, ...contentData } = updateContentDto;

    // Check if sensitive fields are being updated, requiring re-approval
    let requiresReApproval = false;
    if (content.isApproved) {
      if (
        (contentData.title !== undefined && contentData.title !== content.title) ||
        (contentData.body !== undefined && contentData.body !== content.body) ||
        (contentData.contentType !== undefined &&
          contentData.contentType !== content.contentType) ||
        (contentData.templateId !== undefined && contentData.templateId !== content.templateId) ||
        tagIds !== undefined || // Any change in tags requires re-approval
        attachmentIds !== undefined // Any change in attachments requires re-approval
      ) {
        requiresReApproval = true;
      }
    }

    // Update content properties
    Object.assign(content, contentData);

    // Reset approval status if required
    if (requiresReApproval) {
      content.isApproved = false;
    }

    const updatedContent = await this.contentsRepository.save(content);

    // Update tags if provided
    if (tagIds) {
      // First, get current tags
      const currentTags = await this.tagsService.getContentTags(id);
      const currentTagIds = currentTags.map((tag) => tag.id);

      // Remove tags that are not in the new list
      const tagsToRemove = currentTagIds.filter((tagId) => !tagIds.includes(tagId));
      await Promise.all(tagsToRemove.map((tagId) => this.tagsService.removeContentTag(id, tagId)));

      // Add new tags that are not in the current list
      const tagsToAdd = tagIds.filter((tagId) => !currentTagIds.includes(tagId));
      await Promise.all(tagsToAdd.map((tagId) => this.tagsService.addContentTag(id, tagId)));
    }

    // Update attachments if provided
    if (attachmentIds) {
      // First, get current attachments
      const currentAttachments = await this.attachmentsService.findByContentId(id);
      const currentAttachmentIds = currentAttachments.map((attachment) => attachment.id);

      // Remove content association for attachments not in the new list
      const attachmentsToRemove = currentAttachmentIds.filter(
        (attachmentId) => !attachmentIds.includes(attachmentId),
      );
      await Promise.all(
        attachmentsToRemove.map(async (attachmentId) => {
          const attachment = await this.attachmentRepository.findOne({
            where: { id: attachmentId },
          });
          if (attachment) {
            attachment.contentId = null;
            await this.attachmentRepository.save(attachment);
          }
        }),
      );

      // Add new attachments that are not in the current list
      const attachmentsToAdd = attachmentIds.filter(
        (attachmentId) => !currentAttachmentIds.includes(attachmentId),
      );
      await Promise.all(
        attachmentsToAdd.map(async (attachmentId) => {
          const attachment = await this.attachmentRepository.findOne({
            where: { id: attachmentId },
          });
          if (attachment) {
            attachment.contentId = id;
            await this.attachmentRepository.save(attachment);
          }
        }),
      );
    }

    return updatedContent;
  }

  async remove(id: string, userId: string, userRole: UserRole): Promise<void> {
    const content = await this.getContentEntity(id);

    // Check if user is admin or the creator of the content
    if (userRole !== UserRole.ADMIN && content.createdBy !== userId) {
      throw new ForbiddenException('You can only delete your own content');
    }

    // Soft delete the content
    content.isDeleted = true;
    await this.contentsRepository.save(content);
  }

  /**
   * Explicitly approve a content item
   * @param id The ID of the content to approve
   * @returns The updated content with approved status
   */
  async approveContent(id: string): Promise<Content> {
    const content = await this.getContentEntity(id);

    // Set isApproved to true
    content.isApproved = true;

    return this.contentsRepository.save(content);
  }

  /**
   * Explicitly unapprove a content item
   * @param id The ID of the content to unapprove
   * @returns The updated content with unapproved status
   */
  async unapproveContent(id: string): Promise<Content> {
    const content = await this.getContentEntity(id);

    // Set isApproved to false
    content.isApproved = false;

    return this.contentsRepository.save(content);
  }

  // Helper method to get content entity
  private async getContentEntity(id: string): Promise<Content> {
    const content = await this.contentsRepository.findOne({
      where: { id, isDeleted: false },
    });

    if (!content) {
      throw new NotFoundException(`Content with ID ${id} not found`);
    }

    return content;
  }
}
